package com.devyk.av.rtmp.library.camera.renderer

import android.content.Context
import android.graphics.Bitmap
import android.graphics.Color
import android.opengl.GLES20
import com.devyk.av.camera_recorder.callback.IRenderer
import com.devyk.av.rtmp.library.R
import com.devyk.av.rtmp.library.camera.ShaderHelper
import com.devyk.av.rtmp.library.camera.Watermark
import com.devyk.av.rtmp.library.utils.BitmapUtils
import com.devyk.av.rtmp.library.utils.LogHelper
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.FloatBuffer

/**
 * <pre>
 *     author  : devyk on 2020-07-08 17:21
 *     blog    : https://juejin.im/user/578259398ac2470061f3a3fb/posts
 *     github  : https://github.com/yangkun19921001
 *     mailbox : yang1001yk@gmail.com
 *     desc    : This is DrawToEncodeTextureRenderer MediaCodec 编码渲染器
 * </pre>
 */
class DrawToEncodeTextureRenderer(context: Context?, textureId: Int) : IRenderer {


    private var TAG = this.javaClass.simpleName

    /**
     * 顶点坐标
     */
    private val mVertexData: FloatArray = floatArrayOf(
        -1f, -1f,
        1f, -1f,
        -1f, 1f,
        1f, 1f,

        // 水印顶点坐标
        0.55f, -0.9f, //第一个点 左下角
        0.9f, -0.9f,
        0.55f, -0.7f,
        0.9f, -0.7f
    )

    /**
     * 水印坐标
     */
    private var mWatermarkData: FloatArray? = null

    /**
     * 纹理坐标
     */
    private val mFragmentData: FloatArray = floatArrayOf(
        0f, 1f,
        1f, 1f,
        0f, 0f,
        1f, 0f
    )

    /**
     * 定义一个上下文变量
     */
    private var mContext: Context? = null

    /**
     * 执行着色器代码的程序
     */
    private var program = 0

    /**
     * 顶点索引
     */
    private var vPosition = 0

    /**
     * 纹理索引
     */
    private var fPosition = 0

    /**
     * 绘制的纹理 ID
     */
    private var mTextureID = 0

    /**
     * 使用 VBO
     * 概念:
     * - 不使用VBO时，我们每次绘制（ glDrawArrays ）图形时都是从本地内存处获取顶点数据然后传输给OpenGL来绘制，这样就会频繁的操作CPU->GPU增大开销，从而降低效率。
     * - 使用VBO，我们就能把顶点数据缓存到GPU开辟的一段内存中，然后使用时不必再从本地获取，而是直接从显存中获取，这样就能提升绘制的效率。
     *
     */
    private var mVboID = 0


    /**
     * 为顶点坐标分配 native 地址空间
     */
    private lateinit var mVertexBuffer: FloatBuffer

    /**
     * 为片元坐标分配 native 地址空间
     */
    private lateinit var mFragmentBuffer: FloatBuffer


    private var mBitmap: Bitmap? = null
    private var mBitmapTextureId = -1

    init {
        mContext = context
        mTextureID = textureId

        initWatermark()

        mVertexBuffer = ByteBuffer.allocateDirect(mVertexData.size * 4)
            .order(ByteOrder.nativeOrder()) //大内存在前面，字节对齐
            .asFloatBuffer()
            .put(mVertexData)
        //指向第一个索引，相当于 C 里面的第一个内存地址
        mVertexBuffer.position(0)

        mFragmentBuffer = ByteBuffer.allocateDirect(mFragmentData.size * 4)
            .order(ByteOrder.nativeOrder())
            .asFloatBuffer()
            .put(mFragmentData)
        mFragmentBuffer.position(0)
        LogHelper.e(TAG, "TextureId:${mTextureID}")
    }


    fun setTextureId(textureId: Int) {
        mTextureID = textureId
    }


    override fun onSurfaceCreate(width: Int, height: Int) {

        GLES20.glEnable(GLES20.GL_BLEND)
        GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA)


        // 1. 获取顶点/片元源代码资源
        var vertexSource = ShaderHelper.getRawShaderResource(mContext, R.raw.vertex_shader)
        var fragmentSource = ShaderHelper.getRawShaderResource(mContext, R.raw.fragment_shader)

        // 2. 为 顶点和片元创建一个执行程序
        program = ShaderHelper.createProgram(vertexSource, fragmentSource)

        // 3. 拿到顶点/片元 源代码中的索引位置
        vPosition = GLES20.glGetAttribLocation(program, "v_Position")
        fPosition = GLES20.glGetAttribLocation(program, "f_Position")

        // 4. 生成一个 VBO
        var vbo = IntArray(1)
        GLES20.glGenBuffers(1, vbo, 0)
        mVboID = vbo[0]
        // 4.1 绑定 VBO
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mVboID)
        // 4.2 分配 VBO 需要的缓存大小
        GLES20.glBufferData(
            GLES20.GL_ARRAY_BUFFER,
            mVertexData.size * 4 + mFragmentData.size * 4,
            null,
            GLES20.GL_STATIC_DRAW
        )

        // 4.3 为 VBO 设置顶点、片元数据的值
        GLES20.glBufferSubData(GLES20.GL_ARRAY_BUFFER, 0, mVertexData.size * 4, mVertexBuffer)
        GLES20.glBufferSubData(
            GLES20.GL_ARRAY_BUFFER,
            mVertexData.size * 4,
            mFragmentData.size * 4,
            mFragmentBuffer
        )
        // 4.4 解绑 VBO
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0)

        // 创建一个水印
        mBitmapTextureId = createWatermark()
    }

    override fun onSurfaceChange(width: Int, height: Int) {
        GLES20.glViewport(0, 0, width, height)
    }

    override fun onDraw() {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f)

        // 1. 使用顶点和片元创建出来的执行程序
        GLES20.glUseProgram(program)

        // 2. 绑定纹理 (该纹理中保存了相机预览渲染结果，紧接着用于当前线程输入纹理，渲染到当前编码线程对应屏幕中(Surface))
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureID)

        // 3. 绑定 VBO
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mVboID)

        // onSurfaceCreate 方法中，已经将顶点和片段坐标一起绑定到VBO中了, 下面的方法作用是重新设置下各自在VBO的偏移位置
        // 4. 设置顶点坐标在VBO中的偏移量
        GLES20.glEnableVertexAttribArray(vPosition)
        GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 8, 0)

        // 5. 设置纹理坐标在VBO中的偏移量
        GLES20.glEnableVertexAttribArray(fPosition)
        GLES20.glVertexAttribPointer(fPosition, 2, GLES20.GL_FLOAT, false, 8, mVertexData.size * 4)

        // 6. 开始绘制
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4)

        // 渲染水印
        if (mBitmapTextureId != -1) {
            // 绑定水印纹理
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mBitmapTextureId)
            GLES20.glEnableVertexAttribArray(vPosition)
            // 注意这个偏移地址: 32 = 2(每个顶点两个值: x,y) * 4(四个顶点) * 4(每个数据是float类型的，需要乘以4得到每个数据的真实在内存中的长度)
            GLES20.glVertexAttribPointer(
                vPosition, 2, GLES20.GL_FLOAT, false, 8,
                32
            )

            GLES20.glEnableVertexAttribArray(fPosition)
            GLES20.glVertexAttribPointer(
                fPosition, 2, GLES20.GL_FLOAT, false, 8,
                mVertexData.size * 4
            )

            // 绘制水印
            GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4)
        }

        // 7. 解绑
        // 解绑 纹理
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0)
        // 解绑 VBO
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0)
    }


    private fun initWatermark() {
        if (mBitmap == null) {
            mContext?.let { context ->
                mBitmap = BitmapUtils.creatBitmap("  ", context, 20, Color.WHITE, Color.TRANSPARENT)
            }
        }

        // 如果存在自定义坐标，就将自定义坐标放到顶点坐标里面
        mWatermarkData?.let { watermark ->
            mVertexData[8] = watermark[0]
            mVertexData[9] = watermark[1]

            mVertexData[10] = watermark[2]
            mVertexData[11] = watermark[3]

            mVertexData[12] = watermark[4]
            mVertexData[13] = watermark[5]

            mVertexData[14] = watermark[6]
            mVertexData[15] = watermark[7]
        }

        // 重新分配顶点坐标数组，并将最新的值更新到其中
        mVertexBuffer = ByteBuffer.allocateDirect(mVertexData.size * 4)
            .order(ByteOrder.nativeOrder()) // 大内存在前面，字节对齐
            .asFloatBuffer()
            .put(mVertexData)
        // 指向第一个索引，相当于 C 里面的第一个内存地址
        mVertexBuffer.position(0)
    }

    private fun createWatermark(): Int {
        // 创建纹理，并将java中的bitmap数据提供到opengl的内存中（绑定了当前创建的纹理）
        var watermarkId = ShaderHelper.loadBitmapTexture(mBitmap!!)
        // 解绑纹理
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0)
        return watermarkId
    }


    fun setWatermark(watermark: Watermark) {
        // 有文字，就使用文字构建bitmap
        watermark?.txt?.let { txt ->
            mContext?.let { context ->
                mBitmap =
                    BitmapUtils.creatBitmap(
                        txt,
                        context,
                        watermark.textSize,
                        watermark.textColor,
                        Color.TRANSPARENT
                    )
            }
        }

        // 有图片，就用图片
        watermark?.markImg?.let { oldBitmap ->
            mBitmap = oldBitmap
        }


        // 没有设置，就用默认的坐标
        if (watermark.floatArray == null) {
            mBitmap?.let { bitmap ->
                var r = 1.0f * bitmap.getWidth() / bitmap.getHeight()
                var w = r * 0.1f
                mVertexData[8] = 0.7f - w
                mVertexData[9] = -0.9f
                mVertexData[10] = 0.9f
                mVertexData[11] = -0.9f
                mVertexData[12] = 0.7f - w
                mVertexData[13] = -0.75f
                mVertexData[14] = 0.9f
                mVertexData[15] = -0.75f
            }
        }

        // 如果手动设置了坐标，就用手动设置的坐标
        watermark.floatArray?.let {
            this.mWatermarkData = watermark.floatArray
        }
        initWatermark()
        mBitmapTextureId = createWatermark()
    }

}